/*
 Copyright (c) 2013 MeherTJ G. All rights reserved.
 License: LGPL for personnal study or free software.
 */

#pragma once

#include <string>
#include <vector>
#include <cstdint>

namespace Zen
{
	namespace Base64
	{
		int Deamp(uint8_t ch); // char to value

		uint8_t Map(int v64); // value to char

		std::string Encode(std::string const& source);

		std::vector<uint8_t> Decode(std::string const& str);

		std::string Encode(void const* buf, size_t size);

		std::vector<uint8_t> Decode(void const* buf, size_t size);

		bool Check(std::string const& coded);
	}; // namespace Base64
} // namespace Zen

namespace ZenBase64__
{

	inline int CharToValue(uint8_t ch)
	{
		if (ch >= 128) return -1;
		static const int8_t Demaper[128] = {
			-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, //   0-15
			-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, //  16-31
			-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, //  32-47
			52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, //  48-63
			-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, //  64-79
			15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, //  80-95
			-1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, //  96-111
			41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1, // 112-127
		};
		return Demaper[ch];
	}

	inline uint8_t ValueToChar(size_t v64)
	{
		static const char Alphabets[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
		return (v64 >= 64) ? '?' : Alphabets[v64];
	}
} // namespace ZenBase64__

namespace Zen
{
	using namespace ZenBase64__;

	inline std::string Base64::Encode(void const* buf, size_t size)
	{
		std::string res;
		auto dest_size = (size / 3) << 2;
		auto rest = (size % 3);
		if (rest == 0)
			res.resize(dest_size, 0);
		else
			res.resize(dest_size + 4, 0);

		auto value = (uint8_t const*)buf;
		for (size_t i = 0; i < dest_size;)
		{
			unsigned b0 = value[0] >> 2;
			unsigned b1 = (((unsigned)value[0] & 0x3) << 4) | ((unsigned)value[1] >> 4);
			unsigned b2 = (((unsigned)value[1] & 0xf) << 2) | ((unsigned)value[2] >> 6);
			unsigned b3 = value[2] & 0x3f;
			res[i++] = Base64::Map(b0);
			res[i++] = Base64::Map(b1);
			res[i++] = Base64::Map(b2);
			res[i++] = Base64::Map(b3);
			value += 3;
		}
		if (rest == 1)
		{
			unsigned b0 = (unsigned)value[0] >> 2;
			unsigned b1 = ((unsigned)value[0] & 0x3) << 4;
			res[dest_size] = Base64::Map(b0);
			res[dest_size + 1] = Base64::Map(b1);
			res[dest_size + 2] = res[dest_size + 3] = '=';
		}
		else if (rest == 2)
		{
			unsigned b0 = (unsigned)value[0] >> 2;
			unsigned b1 = (((unsigned)value[0] & 0x3) << 4) | ((unsigned)value[1] >> 4);
			unsigned b2 = ((unsigned)value[1] & 0xf) << 2;
			res[dest_size] = Base64::Map(b0);
			res[dest_size + 1] = Base64::Map(b1);
			res[dest_size + 2] = Base64::Map(b2);
			res[dest_size + 3] = '=';
		}
		return res;
	}

	inline std::vector<uint8_t> Base64::Decode(void const* buf, size_t size)
	{
		std::vector<uint8_t> res;
		auto bsize = (size >> 2) * 3;
		if (!bsize)
		{
			return res;
		}
		auto value = (uint8_t const*)buf;
		int rest = 0;

		if (value[size - 1] == '=')
		{
			if (value[size - 2] == '=')
			{
				rest = 1;
				bsize -= 2;
			}
			else
			{
				rest = 2;
				bsize -= 1;
			}
		}
		res.reserve(bsize);
		for (size_t i = 0; i < bsize - rest; i += 3)
		{
			auto v0 = (uint8_t)Base64::Deamp(*value++);
			auto v1 = (uint8_t)Base64::Deamp(*value++);
			auto v2 = (uint8_t)Base64::Deamp(*value++);
			auto v3 = (uint8_t)Base64::Deamp(*value++);
			res.push_back((uint8_t)((v0 << 2) | (v1 >> 4)));
			res.push_back((uint8_t)((v1 << 4) | (v2 >> 2)));
			res.push_back((uint8_t)((v2 << 6) | v3));
		}
		if (rest == 1)
		{
			auto v0 = (uint8_t)Base64::Deamp(*value++);
			auto v1 = (uint8_t)Base64::Deamp(*value++);
			res.push_back((uint8_t)((v0 << 2) | (v1 >> 4)));
		}
		else if (rest == 2)
		{
			auto v0 = (uint8_t)Base64::Deamp(*value++);
			auto v1 = (uint8_t)Base64::Deamp(*value++);
			auto v2 = (uint8_t)Base64::Deamp(*value++);
			res.push_back((uint8_t)((v0 << 2) | (v1 >> 4)));
			res.push_back((uint8_t)((v1 << 4) | (v2 >> 2)));
		}
		return res;
	}

	inline std::string Base64::Encode(std::string const& value)
	{
		return Encode((void const*)value.data(), value.size());
	}

	inline std::vector<uint8_t> Base64::Decode(std::string const& str)
	{
		return Decode((void const*)str.data(), str.size());
	}

	inline bool Base64::Check(std::string const& coded)
	{
		auto size = coded.size();
		if (size == 0) return true;
		if ((size & 0x3) != 0) return false; // __zen_must be (4 bytes x N)

		for (size_t i = 0; i < size - 2; ++i)
		{
			if (Base64::Deamp(coded[i]) == -1) return false;
		}
		auto c1 = coded[size - 1];
		auto c2 = coded[size - 2];
		return (c1 == '=' && c2 == '=') || (c1 == '=' && Base64::Deamp(c2) != -1) || (Base64::Deamp(c1) != -1 && Base64::Deamp(c2) != -1);
	}

	inline int Base64::Deamp(uint8_t ch)
	{
		if (ch > 128) return -1;
		return ZenBase64__::CharToValue(ch & 0x7f);
	}

	inline uint8_t Base64::Map(int v64)
	{
		return ZenBase64__::ValueToChar(v64 & 0x3f);
	}
} // namespace Zen
